/*/////////////////////////////////////////////////////////////////////////////

  The MIT License

  Copyright (c) 2008 - 2010. Dimarik

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.

/////////////////////////////////////////////////////////////////////////////*/

package ru.goodcore.composite
{

//import __AS3__.vec.Vector;

/**
 * 
 * @author					Dimarik
 * @version					1.0
 * @playerversion			Flash 9
 * @langversion				3.0
 * 
 */
public class Branch extends Leaf {

	//-------------------------------------------------------------------------
	//
	//  Constructor
	//
	//-------------------------------------------------------------------------

	/**
	 * Constructor
	 */
	public function Branch() {
		super();
	}

	//-------------------------------------------------------------------------
	//
	//  Properties
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  numChildren
	//---------------------------------

	/**
	 * Returns the number of children of this object.
	 */
	public function get numChildren():uint {
		return this._list.length;
	}

	//-------------------------------------------------------------------------
	//
	//  Private variables
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 */
	//private const _list:	Vector.<Leaf> = new Vector.<Leaf>(); // более тормозной
	private const _list:	Array = new Array();

	//-------------------------------------------------------------------------
	//
	//  Public methods
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  addChild
	//---------------------------------

	/**
	 * Adds a child Leaf instance to this Branch instance.
	 * 
	 * @param	child			The Leaf instance to add as a child of this Branch instance.
	 * 
	 * @return 					The Leaf instance that you pass in the child parameter.
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.ADDED
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.ADDED_TO_CRADLE
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.REMOVED
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.REMOVED_FROM_CRADLE
	 * 
	 * @throws	TypeError		Throws if the child is nill.
	 * 
	 * @throws	ArgumentError	Throws if the child is the same as the parent. 
	 * 							Also throws if the caller is a child (or grandchild etc.) 
	 * 							of the child being added.
	 * 
	 */
	public function addChild(child:Leaf):Leaf {
		if ( !child ) {

			Error.throwError(TypeError, 2007, 'child');
			child = null;

		} else {

			if ( child === this ) {

				Error.throwError(ArgumentError, 2024);
				child = null;

			} else {

				var parent:Branch = super.$parent;

				while ( parent ) {

					if ( parent === child ) {
						Error.throwError(ArgumentError, 2150);
						return null;
					}

					parent = parent.$parent;
				}

				if ( child.$parent === this ) {

					this.$setChildIndex( child, this._list.length );

				} else {

					if (child.$parent)
						child.$parent.removeChild(child);

					this.addChildBefore( child );
					child.$setParent( this );

					this._list.push(child);

				}

			}

		}

		return child;
	}

	//---------------------------------
	//  addChildAt
	//---------------------------------

	/**
	 * Adds a child Leaf instance to this Branch instance. The child is added at 
	 * the index position specified. An index of 0 represents the back (bottom) 
	 * of the composite list for this Branch object.
	 *  
	 * @param	child			The Leaf instance that you pass in the child parameter.
	 * 
	 * @param	index			The index position to which the child is added. If you specify 
	 * 							a currently occupied index position, the child object that exists 
	 * 							at that position and all higher positions are moved up one position 
	 * 							in the child list.
	 * 
	 * @return					The Leaf instance that you pass in the child parameter.
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.ADDED
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.ADDED_TO_CRADLE
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.REMOVED
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.REMOVED_FROM_CRADLE
	 * 
	 * @throws	TypeError		Throws if the child is nill.
	 * 
	 * @throws	ArgumentError	Throws if the child is the same as the parent. 
	 * 							Also throws if the caller is a child (or grandchild etc.) 
	 * 							of the child being added.
	 * 
	 * @throws	RangeError		Throws if the index greater than the child list length.
	 * 
	 */
	public function addChildAt(child:Leaf, index:uint):Leaf {
		if ( !child ) {

			Error.throwError(TypeError, 2007, 'child');
			child = null;

		} else {

			const list:Array = this._list;

			if ( index > list.length ) {

				Error.throwError(RangeError, 2006);
				child = null;

			} else {

				if ( child === this ) {

					Error.throwError(ArgumentError, 2024);
					child = null;

				} else {

					var parent:	Branch = super.$parent;

					while (parent) {

						if (parent === child) {
							Error.throwError(ArgumentError, 2150);
							return null;
						}

						parent = parent.$parent;
					}

					if ( child.$parent === this ) {

						this.$setChildIndex( child, index );

					} else {

						if (child.$parent)
							child.$parent.removeChild(child);

						this.addChildBefore( child );
						child.$setParent( this );
						list.splice( index, 0, child );

					}

				}

			}

		}

		return child;
	}


	//----------------------------------
	//  contains
	//----------------------------------

	/**
	 * Determines whether the specified composite object is a child of the 
	 * <b>Branch</b> instance or the instance itself.
	 * 
	 * @param child		The child object to test.
	 * 
	 * @return			true if the child object is a child of the Branch or the 
	 * 					container itself; otherwise false. The search <b>does not</b>
	 * 					includes the entire tree list including this Branch instance.
	 * 					Grandchildren, great-grandchildren, and so on each return <i>false</i>.
	 */
	public function contains(child:Leaf):Boolean {
		if ( child )
			return child === this || child.$parent === this;

		return false;
	}

	//----------------------------------
	//  getChildAt
	//----------------------------------

	/**
	 * Returns the child leaf object instance that exists at the specified index.
	 * 
	 * @param 	index			The index position of the child object.
	 * 
	 * @return 					The child leaf object at the specified index position.
	 * 
	 * @throws	RangeError		Throws if the index does not exist in the child list.
	 * 
	 */
	public function getChildAt(index:uint):Leaf {
		if ( index < this._list.length )
			return this._list[index] as Leaf;

		Error.throwError(RangeError, 2006);
		return null;
	}

	//----------------------------------
	//  getChildByName
	//----------------------------------

	/**
	 * Returns the child leaf object that exists with the specified name.
	 * 
	 * @param name	The name of the child to return.
	 * 
	 * @return 		The child leaf object with the specified name.
	 * 
	 * 
	 */
	public function getChildByName(name:String):Leaf {
		const list:	Array = this._list;
		const len:	uint = list.length;
		var child:	Leaf;

		for (var i:uint = 0; i < len; i++) {
			child = list[i] as Leaf;
			if (child.name === name) return child;
		}

		return null;
	}

	//----------------------------------
	//  getChildIndex
	//----------------------------------

	/**
	 * Returns the index position of a child Leaf instance.
	 * 
	 * @param child				The Leaf instance to identify.
	 * 
	 * @return 					The index position of the child leaf object to identify.
	 * 
	 * @throws	ArgumentError	Throws if the child parameter is not a child of this object.
	 * 
	 */
	public function getChildIndex(child:Leaf):uint {
		const index:	int = this._list.indexOf(child);

		if (index < 0)
			Error.throwError(ArgumentError, 2025);

		return index;
	}

	//---------------------------------
	//  removeChild
	//---------------------------------

	/**
	 * Removes the specified child Leaf instance from the child list of the Branch instance.
	 * 
	 * @param 	child			The Leaf instance to remove.
	 * 
	 * @return 					The Leaf instance that you pass in the child parameter.
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.REMOVED
	 * 
	 * @events					ru.goodcore.core.composite.CompositeEvent.REMOVED_FROM_CRADLE
	 * 
	 * @throws	TypeError		Throws if the child is nill.
	 * 
	 * @throws	ArgumentError	Throws if the child parameter is not a child of this object.
	 * 
	 */
	public function removeChild(child:Leaf):Leaf {
		if ( !child ) {

			Error.throwError(TypeError, 2007, 'child');
			child = null;

		} else {

			if (child.$parent !== this) {

				Error.throwError(ArgumentError, 2025);
				child = null;

			} else {

				this._list.splice( this._list.indexOf( child ), 1 );
				this.removeChildBefore( child );
				child.$setParent( null );

			}

		}

		return child;
	}

	//---------------------------------
	//  removeChildAt
	//---------------------------------

	/**
	 * Removes a child Leaf from the specified index position in the child list of the Branch.
	 * 
	 * @param 	index		The child index of the Leaf to remove.
	 * 
	 * @return 				The Leaf instance that was removed.
	 * 
	 * @events				ru.goodcore.core.composite.CompositeEvent.REMOVED
	 * 
	 * @events				ru.goodcore.core.composite.CompositeEvent.REMOVED_FROM_CRADLE
	 * 
	 * @throws	RangeError	Throws if the index does not exist in the child list.
	 * 
	 */
	public function removeChildAt(index:uint):Leaf {
		if (index < this._list.length) {

			const child:Leaf = this._list.splice( index, 1 )[0] as Leaf;
			this.removeChildBefore( child );
			child.$setParent( null );
			return child;

		}
		else {

			Error.throwError(RangeError, 2006);
			return null;

		}
	}

	//---------------------------------
	//  setChildIndex
	//---------------------------------

	/**
	 * Changes the position of an existing child in the Branch object container.
	 * 
	 * @param	child			The child Leaf instance for which you want to change the index number.
	 * 
	 * @param 	index			The resulting index number for the child leaf object.
	 * 
	 * @throws	TypeError		Throws if the child is nill.
	 * 
	 * @throws	ArgumentError	Throws if the child parameter is not a child of this object.
	 * 
	 * @throws	RangeError		Throws if the index does not exist in the child list.
	 * 
	 */
	public function setChildIndex(child:Leaf, index:uint):void {
		if ( !child ) {

			Error.throwError(TypeError, 2007, 'child');

		} else {

			if ( child.$parent !== this ) {

				Error.throwError(ArgumentError, 2025);

			} else {

				if ( index < this._list.length ) {
					this.$setChildIndex( child, index );
				}
				else {
					Error.throwError(RangeError, 2006);
				}

			}

		}

	}

	//---------------------------------
	//  swapChildren
	//---------------------------------

	/**
	 * Swaps the z-order (front-to-back order) of the two specified child objects.
	 * All other child objects in the branch container remain in the same index positions.
	 * 
	 * @param	child1			The first child object.
	 * 
	 * @param	child2			The second child object.
	 * 
	 * @throws	TypeError		Throws if either child is nill.
	 * 
	 * @throws	ArgumentError	Throws if either child parameter is not a child of this object.
	 * 
	 */
	public function swapChildren(child1:Leaf, child2:Leaf):void {
		if ( !(child1 && child2) ) {

			Error.throwError(TypeError, 2007, 'child');

		} else {

			if ( child1.$parent !== this || child2.$parent !== this ) {

				Error.throwError(ArgumentError, 2025);

			} else {

				this.$swapChildrenAt( child1, child2, this._list.indexOf(child1), this._list.indexOf(child2) );

			}

		}

	}

	//---------------------------------
	//  swapChildrenAt
	//---------------------------------

	/**
	 * Swaps the z-order (front-to-back order) of the child objects at the two 
	 * specified index positions in the child list.
	 * 
	 * @param 	index1		The index position of the first child object.
	 * 
	 * @param 	index2		The index position of the second child object.
	 * 
	 * @throws	RangeError	If either index does not exist in the child list.
	 * 
	 */
	public function swapChildrenAt(index1:uint, index2:uint):void {
		const list:	Array = this._list;
		const len:	uint = list.length;

		if (index1 < len && index2 < len) {

			this.$swapChildrenAt(list[index1], list[index2], index1, index2);

		} else {

			Error.throwError(RangeError, 2006);

		}

	}

	//---------------------------------
	//  removeAllChildren
	//---------------------------------

	/**
	 * Clears container
	 */
	public function removeAllChildren():void {
		const list:	Array = this._list;
		var child:	Leaf;

		while (list.length) {
			child = list.pop() as Leaf;
			this.removeChildBefore( child );
			child.$setParent( null );
		}
	}

	//-------------------------------------------------------------------------
	//
	//  Protected methods
	//
	//-------------------------------------------------------------------------

	protected function addChildBefore(child:Leaf):void {
		
	}

	protected function removeChildBefore(child:Leaf):void {
		
	}

	//-------------------------------------------------------------------------
	//
	//  Private methods
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 */
	private function $setChildIndex(child:Leaf, index:uint):void {
		const list:	Array = this._list;
		list.splice( list.indexOf(child), 1 );
		list.splice( index, 0, child );
	}

	/**
	 * @private
	 */
	private function $swapChildrenAt(child1:Leaf, child2:Leaf, index1:uint, index2:uint):void {
		if ( index1 > index2 ) {
			this.$setChildIndex( child1, index2 );
			this.$setChildIndex( child2, index1 );
		} else {
			this.$setChildIndex( child2, index1 );
			this.$setChildIndex( child1, index2 );
		}
	}

}
}